#include "STips.h"
#include <QApplication>
#include <QDesktopWidget>
#include <QEvent>
#include <QStyle>
#include <QStyleOption>
#include <QStylePainter>
#include <QTextDocument>
#include <QScreen>
#include <QMouseEvent>

namespace qb
{
	QBTipLabel* QBTipLabel::instance = nullptr;

	QBTipLabel::QBTipLabel(const QString& text, const QPoint& pos, QWidget* w, int msecDisplayTime, bool html)
		: QLabel(w, Qt::ToolTip | Qt::BypassGraphicsProxyWidget), widget(nullptr), m_html(html)
	{
		delete instance;
		instance = this;
		setForegroundRole(QPalette::ToolTipText);
		setBackgroundRole(QPalette::ToolTipBase);
		setPalette(QBTips::palette());
		setWordWrap(true);
		ensurePolished();
		setMargin(1 + style()->pixelMetric(QStyle::PM_ToolTipLabelFrameWidth, nullptr, this));
		setFrameStyle(QFrame::NoFrame);
		setAlignment(Qt::AlignLeft);
		setIndent(1);
		qApp->installEventFilter(this);
		setWindowOpacity(style()->styleHint(QStyle::SH_ToolTipLabel_Opacity, nullptr, this) / 255.0);
		setMouseTracking(true);
		fadingOut = false;
		border = QColor(47, 82, 75, 255);
		reuseTip(text, msecDisplayTime, pos);
	}

	void QBTipLabel::restartExpireTimer(int msecDisplayTime)
	{
		if (-1 == msecDisplayTime)
		{
			return;
		}

		int time = 10000 + 40 * qMax(0, text().length() - 100);
		if (msecDisplayTime > 0)
			time = msecDisplayTime;
		expireTimer.start(time, this);
		hideTimer.stop();
	}

	void QBTipLabel::reuseTip(const QString& text, int msecDisplayTime, const QPoint& pos)
	{
		setText(text);
		updateSize(pos);
		restartExpireTimer(msecDisplayTime);
	}

	void QBTipLabel::updateSize(const QPoint& pos)
	{
		// Ensure that we get correct sizeHints by placing this window on the right screen.
		QFontMetrics fm(font());
		QSize extra(1, 0);
		// Make it look good with the default ToolTip font on Mac, which has a small descent.
		if (fm.descent() == 2 && fm.ascent() >= 11)
			++extra.rheight();
		setWordWrap(Qt::mightBeRichText(text()));
		QSize sh = sizeHint();
		// ### When the above WinRT code is fixed, windowhandle should be used to find the screen.
		QScreen* screen = QGuiApplication::screenAt(pos);
		if (!screen)
			screen = QGuiApplication::primaryScreen();
		if (screen) {
			const qreal screenWidth = screen->geometry().width();
			if (!wordWrap() && sh.width() > screenWidth) {
				setWordWrap(true);
				sh = sizeHint();
			}
		}
		resize(sh + extra);
	}

	void QBTipLabel::paintEvent(QPaintEvent* ev)
	{
		if (m_html)
			return QLabel::paintEvent(ev);

		QStylePainter p(this);
		QStyleOptionFrame opt;

		QRect crAll = contentsRect(); 
		QRect cr = crAll;
		cr.adjust(margin(), margin(), -margin(), -margin());

		opt.init(this);
		p.fillRect(crAll, QBTips::palette().toolTipBase());

		QPen oldPen = p.pen();
		p.setPen(border);
		p.drawRect(0, 0, crAll.width() - 1, crAll.height() - 1);
		p.setPen(oldPen);

		QRectF rect(0, 0, 10000, 10000);
		int nFlag = Qt::TextDontClip | Qt::AlignLeft | Qt::AlignVCenter;
		QRectF spaaceRect = p.boundingRect(rect, nFlag, " ");

		QStringList rlist = text().split('\n');
		int rSize = rlist.size();
		for (int i = 0; i < rSize; i++)
		{
			int nLastWidth = 0;
			QStringList clist = rlist.at(i).split(' ');
			for (int j = 0; j < clist.size(); j++)
			{
				QPen oldPen;
				if (tipItems[i][j].bCustom)
				{
					oldPen = p.pen();
					p.setPen(tipItems[i][j].clrNormal);
				}

				QRectF measureRect = p.boundingRect(rect, nFlag, clist.at(j));
				p.drawText(cr.left() + nLastWidth, cr.top() + cr.height() / rSize * i, cr.width(), cr.height() / rSize, nFlag, clist.at(j));

				if (tipItems[i][j].bCustom)
				{
					p.setPen(oldPen);
				}

				nLastWidth += measureRect.width() + spaaceRect.width();
			}
		}

	//	p.drawPrimitive(QStyle::PE_PanelTipLabel, opt);
		p.end();

	//	QLabel::paintEvent(ev);
	}

	void QBTipLabel::resizeEvent(QResizeEvent* e)
	{
		QStyleHintReturnMask frameMask;
		QStyleOption option;
		option.init(this);
		if (style()->styleHint(QStyle::SH_ToolTip_Mask, &option, this, &frameMask))
			setMask(frameMask.region);

		QLabel::resizeEvent(e);
	}

	void QBTipLabel::mouseMoveEvent(QMouseEvent* e)
	{
		if (!rect.isNull()) {
			QPoint pos = e->globalPos();
			if (widget)
				pos = widget->mapFromGlobal(pos);
			if (!rect.contains(pos))
				hideTip();
		}
		QLabel::mouseMoveEvent(e);
	}

	QBTipLabel::~QBTipLabel()
	{
		instance = nullptr;
	}

	void QBTipLabel::hideTip()
	{
		if (!hideTimer.isActive())
			hideTimer.start(300, this);
	}

	void QBTipLabel::hideTipImmediately()
	{
		close(); // to trigger QEvent::Close which stops the animation
		deleteLater();
	}

	void QBTipLabel::setTipRect(QWidget* w, const QRect& r)
	{
		if (Q_UNLIKELY(!r.isNull() && !w)) {
			qWarning("QToolTip::setTipRect: Cannot pass null widget if rect is set");
			return;
		}
		widget = w;
		rect = r;
	}

	void QBTipLabel::timerEvent(QTimerEvent* e)
	{
		if (e->timerId() == hideTimer.timerId()
			|| e->timerId() == expireTimer.timerId()) {
			hideTimer.stop();
			expireTimer.stop();
			hideTipImmediately();
		}
	}

	bool QBTipLabel::eventFilter(QObject* o, QEvent* e)
	{
		switch (e->type()) {
#ifdef CCORE_MAC
		case QEvent::KeyPress:
		case QEvent::KeyRelease: {
			const int key = static_cast<QKeyEvent*>(e)->key();
			// Anything except key modifiers or caps-lock, etc.
			if (key < Qt::Key_Shift || key > Qt::Key_ScrollLock)
				hideTipImmediately();
			break;
		}
#endif
		case QEvent::Leave:
			hideTip();
			break;

		case QEvent::WindowActivate:
		case QEvent::WindowDeactivate:
		case QEvent::FocusIn:
		case QEvent::FocusOut:
		case QEvent::Close: // For QTBUG-55523 (QQC) specifically: Hide tooltip when windows are closed
		case QEvent::MouseButtonPress:
		case QEvent::MouseButtonRelease:
		case QEvent::MouseButtonDblClick:
		case QEvent::Wheel:
			hideTipImmediately();
			break;

		case QEvent::MouseMove:
			if (o == widget && !rect.isNull() && !rect.contains(static_cast<QMouseEvent*>(e)->pos()))
				hideTip();
		default:
			break;
		}
		return false;
	}

	int QBTipLabel::getTipScreen(const QPoint& pos, QWidget* w)
	{
		QDesktopWidget* d = QApplication::desktop();
		if (d->isVirtualDesktop())
			return d->screenNumber(pos);
		else
			return d->screenNumber(w);
	}

	void QBTipLabel::placeTip(const QPoint& pos, QWidget* w)
	{
		QPoint p = pos;
		const QScreen* screen = QGuiApplication::screens().value(getTipScreen(pos, w),
			QGuiApplication::primaryScreen());
		// a QScreen's handle *should* never be null, so this is a bit paranoid
		if (const QPlatformScreen* platformScreen = screen ? screen->handle() : nullptr) {
			//     QPlatformCursor* cursor = platformScreen->cursor();
				 // default implementation of QPlatformCursor::size() returns QSize(16, 16)
			const QSize nativeSize = QSize(16, 16);
			const QSize cursorSize = nativeSize;
			QPoint offset(10, cursorSize.height()+2);
			// assuming an arrow shape, we can just move to the side for very large cursors
			if (cursorSize.height() > 2 * this->height())
				offset = QPoint(cursorSize.width() / 2, 0);

			p += offset;

			QRect screenRect = screen->geometry();
			if (p.x() + this->width() > screenRect.x() + screenRect.width())
				p.rx() -= 4 + this->width();
			if (p.y() + this->height() > screenRect.y() + screenRect.height())
				p.ry() -= 24 + this->height();
			if (p.y() < screenRect.y())
				p.setY(screenRect.y());
			if (p.x() + this->width() > screenRect.x() + screenRect.width())
				p.setX(screenRect.x() + screenRect.width() - this->width());
			if (p.x() < screenRect.x())
				p.setX(screenRect.x());
			if (p.y() + this->height() > screenRect.y() + screenRect.height())
				p.setY(screenRect.y() + screenRect.height() - this->height());
		}
		this->move(p);
	}

	void QBTipLabel::setCustomItem(int row, int col, QColor clrNormal)
	{
		if (row < 0 || row >= QBTIP_MAX_ITEM_COUNT || col < 0 || col >= QBTIP_MAX_ITEM_COUNT)
		{
			return;
		}

		tipItems[row][col].bCustom = true;
		tipItems[row][col].clrNormal = clrNormal;
	}

	void QBTipLabel::setBorderColor(QColor clrBorder)
	{
		border = clrBorder;
	}


	void QBTipLabel::setHtml(bool v)
	{
		m_html = v;
		updateSize({ 0,0 });
		update();
	}

	bool QBTipLabel::tipChanged(const QPoint& pos, const QString& text, QObject* o)
	{
		if (QBTipLabel::instance->text() != text)
			return true;

		if (o != widget)
			return true;

		if (!rect.isNull())
			return !rect.contains(pos);
		else
			return false;
	}

	void QBTips::showText(const QPoint& pos, const QString& text, QWidget* w, std::vector<QBTipCustomItem>* vctCustomItem, bool html, int margin, int msecShowTime, QColor border)
	{
		QRect rect(0, 0, 200, 100);
		if (QBTipLabel::instance && QBTipLabel::instance->isVisible()) { // a tip does already exist
			if (text.isEmpty()) { // empty text means hide current tip
				QBTipLabel::instance->hideTip();
				return;
			}
			else if (!QBTipLabel::instance->fadingOut) {
				// If the tip has changed, reuse the one
				// that is showing (removes flickering)
				QPoint localPos = pos;
				if (w)
					localPos = w->mapFromGlobal(pos);
				if (QBTipLabel::instance->tipChanged(localPos, text, w)) {
					if (vctCustomItem)
					{
						for (int i = 0; i < vctCustomItem->size(); i++)
						{
							QBTipLabel::instance->setCustomItem((*vctCustomItem)[i].row, (*vctCustomItem)[i].col, (*vctCustomItem)[i].clrNormal);
						}
					}
					QBTipLabel::instance->setBorderColor(border);
					QBTipLabel::instance->setMargin(margin);
					QBTipLabel::instance->reuseTip(text, msecShowTime, pos);
					QBTipLabel::instance->setTipRect(w, rect);
					QBTipLabel::instance->placeTip(pos, w);
					QBTipLabel::instance->setHtml(html);
				}
				return;
			}
		}

		if (!text.isEmpty()) { // no tip can be reused, create new tip:
#ifdef Q_OS_WIN32
		// On windows, we can't use the widget as parent otherwise the window will be
		// raised when the tooltip will be shown
			QT_WARNING_PUSH
			QT_WARNING_DISABLE_DEPRECATED
			new QBTipLabel(text, pos, QApplication::desktop()->screen(QBTipLabel::getTipScreen(pos, w)), msecShowTime, html);
			QT_WARNING_POP
#else
			new QBTipLabel(text, pos, w, msecShowTime); // sets QTipLabel::instance to itself
#endif
			if (vctCustomItem)
			{
				for (int i = 0; i < vctCustomItem->size(); i++)
				{
					QBTipLabel::instance->setCustomItem((*vctCustomItem)[i].row, (*vctCustomItem)[i].col, (*vctCustomItem)[i].clrNormal);
				}
			}
			QBTipLabel::instance->setBorderColor(border);
			QBTipLabel::instance->setMargin(margin);
			QBTipLabel::instance->setTipRect(w, rect);
			QBTipLabel::instance->placeTip(pos, w);
			QBTipLabel::instance->setObjectName(QLatin1String("qtooltip_label"));
			QBTipLabel::instance->showNormal();
			QBTipLabel::instance->setHtml(html);
		}
	}

	void QBTips::hideTip()
	{
		if (QBTipLabel::instance)
			QBTipLabel::instance->hideTipImmediately();
	}

	bool QBTips::isVisible()
	{
		return (QBTipLabel::instance != nullptr && QBTipLabel::instance->isVisible());
	}

	QString QBTips::text()
	{
		if (QBTipLabel::instance)
			return QBTipLabel::instance->text();
		return QString();
	}

	Q_GLOBAL_STATIC(QPalette, tooltip_palette)

	QPalette QBTips::palette()
	{
		return *tooltip_palette();
	}

	void QBTips::setPalette(const QPalette& palette)
	{
		*tooltip_palette() = palette;
		if (QBTipLabel::instance)
			QBTipLabel::instance->setPalette(palette);
	}

	QFont QBTips::font()
	{
		return QApplication::font("QTipLabel");
	}

	void QBTips::setFont(const QFont& font)
	{
		QApplication::setFont(font, "QBTipLabel");
	}
}